#!/usr/bin/env jruby
# -*- coding: us-ascii -*-
# Elizabeth Wiethoff 2008

require 'java'
require 'pp' if $DEBUG
require 'csv'
require 'singleton'

dir = File.dirname(__FILE__)
require File.join(dir, '..', 'auxil', 'javainclude')
require File.join(dir, '..', 'auxil', 'osdetect')
require File.join(dir, '..', 'gui', 'titledborder')
require File.join(dir, '..', 'monkey', 'array2vector')
require File.join(dir, '..', 'monkey', 'time')
require File.join(dir, 'filedialogs')

########################################

class FileGroupTCRenderer < javax.swing.table.DefaultTableCellRenderer
  include Singleton
  include_class 'java.awt.Color'
  
  FORTRAN_GREEN1 = Color.new(0.25, 0.75, 0.25, 0.25)
  FORTRAN_GREEN2 = Color.new(200, 235, 200)
  GREEN_COLOR = FORTRAN_GREEN2
  WHITE_COLOR = Color::WHITE
  
  def initialize
    super
    specializeInitialization  # template method pattern
  end
  
  def getTableCellRendererComponent(table, obj, isSel, foc, row, col)
    super(table, obj, isSel, foc, row, col)
    if isSel
      setForeground(table.getForeground)
      setBackground(table.getSelectionBackground)
    else
      gno = table.getModel.getValueAt(row, 0)  # how know it's 0 ?
      setBackground((gno % 2).zero? ? WHITE_COLOR : GREEN_COLOR)
    end
    specializeObjectRendering(obj)  # template method pattern
    return self
  end
  
  protected
  def specializeInitialization ; end  # subclasses override
  def specializeObjectRendering(obj) ; end  # subclasses override
end

########################################

class FixnumTCRenderer < FileGroupTCRenderer
  include_class 'java.text.DecimalFormat'
  include_class 'javax.swing.JLabel'
  
  @@formatter = DecimalFormat.getIntegerInstance
  
  private
  
  def specializeInitialization
    setHorizontalAlignment(JLabel::RIGHT)
  end
  
  def specializeObjectRendering(integer)
    setText(@@formatter.format(integer))
  end
end

BignumTCRenderer = FixnumTCRenderer  # alias

########################################

class StringTCRenderer < FileGroupTCRenderer
  private
  def specializeObjectRendering(string)
    setToolTipText(string)
  end
end

########################################

class TimeTCRenderer < FileGroupTCRenderer
  include_class 'javax.swing.JLabel'
  
  private
  
  def specializeInitialization
    setHorizontalAlignment(JLabel::CENTER)
  end
  
  def specializeObjectRendering(time)
    setText(time.to_s)  # monkey-patched Time format
  end
end

########################################

class FileGroupTable < javax.swing.JTable
  def getCellRenderer(row, column)
    # use convertColumnIndexToModel so that columns still 
    # render correctly after user moves them
    columnInModel = convertColumnIndexToModel(column)
    obj = getModel.getValueAt(0, columnInModel)
    return Module.const_get("#{obj.class}TCRenderer").instance
  end
end

########################################

class SpiffyComparator
  include java.util.Comparator
  
  def initialize(column, meth=:<=>, mult=1)
    @column = column
    @meth = meth
    @mult = mult
  end
  attr_reader :column
  protected :column
  
  def compare(a, b)
    a.elementAt(@column).send(@meth, b.elementAt(@column)) * @mult
  end
  
  def equals(other)
    @column = other.column and 
    @meth = other.meth and 
    @mult = other.mult
  end
end

########################################

class FileGroupTableModel < javax.swing.table.DefaultTableModel
  include java.awt.event.ActionListener
  
  def initialize(data, columnNames)
    super(data.to_vov, columnNames.to_vector)
  end
  
  def isCellEditable(row, column)
    false
  end
  
  def actionPerformed(event)
    klass = event.getSource.class
    radioActionPerformed(event) if klass == javax.swing.JRadioButton
    menuActionPerformed(event) if klass == javax.swing.JMenuItem
  end
  
  def dump(path)
    hack_line_ending  # for $/
    headings = (0...getColumnCount).map { |i| getColumnName(i) }
    data = Array.from_vov(getDataVector)
    CSV.open(path, 'w', "\t", $/) do |writer|
      writer << headings
      data.each { |row| writer << row }
    end
  end
  
  GNO_COMPARATOR = SpiffyComparator.new 0
  DIRECTORY_COMPARATOR = SpiffyComparator.new 1, :casecmp
  FILENAME_COMPARATOR = SpiffyComparator.new 2, :casecmp
  MODTIME_COMPARATOR = SpiffyComparator.new 3
  FILESIZE_COMPARATOR = SpiffyComparator.new 4
  GSZ_COMPARATOR = SpiffyComparator.new 5
  
  GNO_REVERSE_COMPARATOR = SpiffyComparator.new 0, :<=>, -1
  DIRECTORY_REVERSE_COMPARATOR = SpiffyComparator.new 1, :casecmp, -1
  FILENAME_REVERSE_COMPARATOR = SpiffyComparator.new 2, :casecmp, -1
  MODTIME_REVERSE_COMPARATOR = SpiffyComparator.new 3, :<=>, -1
  FILESIZE_REVERSE_COMPARATOR = SpiffyComparator.new 4, :<=>, -1
  GSZ_REVERSE_COMPARATOR = SpiffyComparator.new 5, :<=>, -1
  
  protected
  
  def radioActionPerformed(event)
    command = event.getActionCommand
    col_name = command.split.first
    comparator = if (command =~ /9-0$/ or command =~ /Z-A$/i)
      self.class.const_get "#{col_name}_REVERSE_COMPARATOR".upcase!
    else
      self.class.const_get "#{col_name}_COMPARATOR".upcase!
    end
    java.util.Collections.sort(getDataVector, comparator)
    fireTableRowsUpdated(0, getRowCount-1)
  end
  
  def menuActionPerformed(event)
    command = event.getActionCommand
    case command
      when /Quit/
        java.lang.System.exit(0)
      when /Export/
        # really should get default from YAML
        default = File.expand_path('~/Desktop/superelse.out.csv')
        doit, path = FileDialogs.chooseExportFile(default)
        dump(path) if doit
      else
        puts "pretending to #{command}"
    end
  end
end

########################################

module OutputBoxJavaClasses
  include_classes 'java.awt', %w[BorderLayout Dimension GridLayout]
  include_classes 'java.awt.event', %w[KeyEvent]
  include_classes 'javax.swing', %w[BorderFactory ButtonGroup KeyStroke 
    JFrame JMenu JMenuBar JMenuItem JPanel JRadioButton JScrollPane]
end

JO = OutputBoxJavaClasses  # alias

########################################

def assembleRadios(column_info)
  pRadios = JO::JPanel.new(JO::GridLayout.new(2, column_info.size))
  pRadios.setBorder(bigGrayTitledBorder('Sort by'))
  
  buttonGroup = JO::ButtonGroup.new
  
  column_info.each do |hash|
    order = hash[:type] == String ? 'A-Z' : '0-9'
    radio = JO::JRadioButton.new("#{hash[:title]} #{order}")
    pRadios.add(radio)
    buttonGroup.add(radio)
  end
  
  column_info.each do |hash|
    order = hash[:type] == String ? 'Z-A' : '9-0'
    radio = JO::JRadioButton.new("#{hash[:title]} #{order}")
    pRadios.add(radio)
    buttonGroup.add(radio)
  end
  
  pRadios.getComponents[0].setSelected(true)  # how know it's 0 ?
  return pRadios
end

########################################

def assembleTable(data_table, column_info)
  column_names = column_info.map { |hash| hash[:title] }
  tableModel = FileGroupTableModel.new(data_table, column_names)
  
  table = FileGroupTable.new(tableModel)
  
  columnModel = table.getColumnModel
  column_info.each do |hash|
    column = columnModel.getColumn(hash[:column])
    column.setPreferredWidth(hash[:width])
    column.setMinWidth(hash[:width])
    case hash[:title]
      when /^G\w\w$/i : column.setMaxWidth(hash[:width] * 4)
      when /Time$/i : column.setMaxWidth(hash[:width] * 1.2)
      when /^File\s?Size/i : column.setMaxWidth(hash[:width] * 1.5)
    end
  end
  
  return table
end

########################################

def assembleFileMenu(tableModel)
  $SCREEN_MENU_BAR = $MAC
  $KMASK = $MAC ? 'meta ' : 'ctrl '
  
  f = JO::JMenu.new('File')
  o = JO::JMenuItem.new('Open File')
  g = JO::JMenuItem.new('Open All Files in Group')
  r = JO::JMenuItem.new('Rename File...')
  m = JO::JMenuItem.new('Move File...')
  t = JO::JMenuItem.new('Toss File')
  e = JO::JMenuItem.new('Export Table...')
  
  unless $SCREEN_MENU_BAR
    f.setMnemonic(JO::KeyEvent::VK_F)
    o.setMnemonic(JO::KeyEvent::VK_O)
    g.setMnemonic(JO::KeyEvent::VK_G)
    r.setMnemonic(JO::KeyEvent::VK_R)
    m.setMnemonic(JO::KeyEvent::VK_M)
    t.setMnemonic(JO::KeyEvent::VK_T)
    e.setMnemonic(JO::KeyEvent::VK_E)
  end
  
  o.setAccelerator(JO::KeyStroke.getKeyStroke($KMASK + 'O'))
  g.setAccelerator(JO::KeyStroke.getKeyStroke($KMASK + 'alt O'))
  r.setAccelerator(JO::KeyStroke.getKeyStroke($KMASK + 'R'))
  m.setAccelerator(JO::KeyStroke.getKeyStroke($KMASK + 'M'))
  t.setAccelerator(JO::KeyStroke.getKeyStroke($KMASK + 'T'))
  e.setAccelerator(JO::KeyStroke.getKeyStroke($KMASK + 'E'))
  
  [o, g, r, m, t, e].each { |item| item.addActionListener(tableModel) }
  
  [o, g, r, m, t].each { |item| f.add(item) }
  f.addSeparator
  f.add(e)
  
  unless $MAC
    f.addSeparator
    q = JO::JMenuItem.new('Quit')
    q.setMnemonic(JO::KeyEvent::VK_Q)
    q.setAccelerator(JO::KeyStroke.getKeyStroke($KMASK + 'Q'))
    q.addActionListener(tableModel)
    f.add(q)
  end
  
  return f
end

########################################

def assembleMenuBar(*menus)
  mb = JO::JMenuBar.new
  menus.each { |menu| mb.add(menu) }
  return mb
end

########################################

def launchOutputWindow(data_table, column_info)
  # make radio buttons first to avoid Mac bug
  pRadios = assembleRadios(column_info)
  table = assembleTable(data_table, column_info)
  
  # I'd like to do this elsewhere, not here. Oh, well.
  pRadios.getComponents.each do |radio|
    radio.addActionListener(table.getModel)
  end
  
  spTable = JO::JScrollPane.new(table)
  spTable.setPreferredSize(JO::Dimension.new(800, 400))
  
  fileMenu = assembleFileMenu(table.getModel)
  menuBar = assembleMenuBar(fileMenu)
  
  mainFrame = JO::JFrame.new('SuperElse Output')
  mainFrame.setDefaultCloseOperation(JO::JFrame::DISPOSE_ON_CLOSE)
  mainFrame.setJMenuBar(menuBar)
  
  mainContentPane = mainFrame.getContentPane
  mainContentPane.setLayout(JO::BorderLayout.new)
  mainContentPane.add(spTable, JO::BorderLayout::CENTER)
  mainContentPane.add(pRadios, JO::BorderLayout::SOUTH)
  
  mainFrame.pack
  mainFrame.setVisible(true)
  return mainFrame
end
